package com.naiterui.ehp.bs.im.service.message.handler;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.consult.SessionCloseBO;
import com.naiterui.ehp.bp.bo.im.SessionDetailBO;
import com.naiterui.ehp.bp.bo.im.SessionParamBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.DoctorPatientRelation;
import com.naiterui.ehp.bp.constants.ImConstants;
import com.naiterui.ehp.bp.constants.PayStatus;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.im.config.MediaFileConfig;
import com.naiterui.ehp.bs.im.config.MqttConnectionConfig;
import com.naiterui.ehp.bs.im.exception.ExceptionCodes;
import com.naiterui.ehp.bs.im.exception.NaiteruiIMException;
import com.naiterui.ehp.bs.im.feign.DoctorFeginClient;
import com.naiterui.ehp.bs.im.feign.PatientFeginClient;
import com.naiterui.ehp.bs.im.network.protocl.CustomizedMessage;
import com.naiterui.ehp.bs.im.network.protocl.Extend;
import com.naiterui.ehp.bs.im.network.protocl.Media;
import com.naiterui.ehp.bs.im.network.protocl.Message;
import com.naiterui.ehp.bs.im.network.protocl.Session;
import com.naiterui.ehp.bs.im.network.protocl.SessionDetails;
import com.naiterui.ehp.bs.im.network.protocl.SessionParams;
import com.naiterui.ehp.bs.im.remote.IRemoter;
import com.naiterui.ehp.bs.im.repository.mongo.MessageDao;
import com.naiterui.ehp.bs.im.repository.mongo.Page;
import com.naiterui.ehp.bs.im.repository.mongo.SessionDao;
import com.naiterui.ehp.bs.im.utils.Constants;
import com.naiterui.ehp.bs.im.vo.ConsultPayVO;
import com.naiterui.ehp.bs.im.vo.PageVO;
import com.naiterui.ehp.bs.im.vo.SessionConditionCountVO;
import com.naiterui.ehp.bs.im.vo.SessionConditionVO;
import com.naiterui.ehp.bs.im.vo.SessionVO;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SessionHandler {

    /**
     * 是否第一条患者消息(0:不是;1:是)
     */
    public static final int IS_FIRST_MSG = 1;
    /**
     * 是否第一条患者消息(0:不是;1:是)
     */
    public static final int NOT_FIRST_MSG = 0;
    private static final Logger LOGGER = LoggerFactory.getLogger(SessionHandler.class);
    /**
     * 咨询中
     */
    private static final int IN_ASK = -1;
    /**
     * 未支付
     */
    private static final int NOT_PAY = 1;
    /**
     * 已支付
     */
    private static final int HAVE_PAID = 1;
    /**
     * 消息数据持久化对象
     */
    private final SessionDao sessionDao;
    /**
     * 远程调用方法封装
     */
    private final IRemoter remoter;
    /**
     * 消息数据持久化对象
     */
    private final MessageDao messageDao;
    /**
     * 配置信息
     */
    private final MqttConnectionConfig mqttConnectionConfig;

    private final PatientFeginClient patientFeginClient;

    private final MediaFileConfig mediaConfig;
    private final DoctorFeginClient doctorFeginClient;


    public SessionHandler(SessionDao sessionDao, IRemoter remoter, MessageDao messageDao, MqttConnectionConfig mqttConnectionConfig, PatientFeginClient patientFeginClient, MediaFileConfig mediaConfig, DoctorFeginClient doctorFeginClient) {
        this.sessionDao = sessionDao;
        this.remoter = remoter;
        this.messageDao = messageDao;
        this.mqttConnectionConfig = mqttConnectionConfig;
        this.patientFeginClient = patientFeginClient;
        this.mediaConfig = mediaConfig;
        this.doctorFeginClient = doctorFeginClient;
    }

    /**
     * 会话处理
     */
    public SessionVO handleSession(Message<?> message, boolean ignoreCharge) throws BusinessException {
        LOGGER.info("开始会话处理, message:{}, ignoreCharge:{}", JSONUtil.toJsonStr(message), ignoreCharge);
        // 返回结果
        SessionVO vo = null;

        // 是否来自医生端
        boolean isFromDct = message.getRelation() == Constants.MESSAGE_RESOURCE_DOCTOR;

        Long doctorId = isFromDct ? message.getFrom().getId() : message.getTo().getId();
        Long patientId = isFromDct ? message.getTo().getId() : message.getFrom().getId();

        /*
         * 检测患者是否被删除
         */
        String doctorPatientRelationRedisKey = DoctorPatientRelation.getRedisKey(doctorId, patientId);
        if (RedisUtil.keyOps().existsKey(doctorPatientRelationRedisKey)) {
            throw new NaiteruiIMException(ExceptionCodes.PATIENT_HADBEEN_DELETE_ERROR);
        }

        String sessionKey = getSessionKey(doctorId, patientId);

        // 如果为医生发送，则需要设置会话状态
        if (isFromDct) {
            ThreadPoolUtil.execute(() -> {
                this.setConsultSessionStatus(message.getSession().getSessionId(), doctorId, patientId);
            });
        }

        // 检查患者端第一条文本消息
        //int isFirst = this.checkFirstMsg(isFromDct, doctorId, patientId, sessionKey);
        int isFirst = NOT_FIRST_MSG;
        Integer consultPayType = Constants.CONSULT_PAY_TYPE_FREE;// 默认初始化：免费会话
        // 如果messge中无session
        if (message.getSession() == null || StringUtils.isBlank(message.getSession().getSessionId())) {
            boolean isFirstBind = RedisUtil.keyOps().existsKey(CommonConstant.PATIENT_PERSONAL_DOCTOR_FIRST_BIND + doctorId + "_" + patientId);
            // 如果是来自患者端并且不是患者首次扫码绑定医生就不生成session信息
            int messageType = message.getType();
            // 来自医生端并且不是首次，类型不能是处方、病历、病情系统消息、结束咨询系统消息
            if(isFromDct && !isFirstBind && messageType != Constants.MESSAGE_TYPE_PATIENTS_SERVICE_EVALUATION
                    && messageType != Constants.MESSAGE_TYPE_DOCTORS_MEDICALRECORD
                && messageType != Constants.MESSAGE_TYPE_DRUG && messageType != ImConstants.MESSAGE_TYPE_FOLLOW_UP) {
                return null;
            }

            // 生成sessionId
            String sessionId = sessionKey + "_" + System.currentTimeMillis();
            /*
             * 添加付费咨询逻辑
             * 1.先判断当前是否有进行中的有效会话
             * 2.如果消息来自患者端,且当前无进行中的有效会话则判断患者是否已经支付过会话 @Anchor1
             */
            // 如果redis中无session
            if (!RedisUtil.hashOps().hexist(sessionKey, "sessionId")) {
                /*
                 * Anchor1
                 */
                Integer consultSourceType = SessionDetails.CONSULT_TYPE_GENERAL;// 咨询类型 0-默认普通会话
                // 来自患者
//                if (!isFromDct && !ignoreCharge) {
                if (!ignoreCharge) {
                    // 检查患者是否需要支付会话
                    ConsultPayVO consultPayVo = remoter.requirePaySession(patientId, doctorId);
                    if (consultPayVo == null) {
                        throw new NaiteruiIMException(ExceptionCodes.SESSION_PAY_INFO_GET_ERROR);
                    }
                    Integer needCharge = consultPayVo.getFlag();
                    if (needCharge == null) {
                        throw new NaiteruiIMException(ExceptionCodes.SESSION_PAY_INFO_GET_ERROR);
                    }
                    // 首次绑定
                    if(isFirstBind){
                        Long inquirerId = patientFeginClient.getMyself(patientId);
                        LOGGER.info("患者首次绑定医生，获取患者就诊人id：{}", inquirerId);
                        // 切换该医生为当前咨询医生
                        RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM, patientId.toString(), doctorId.toString());
                        RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM_NAME, patientId.toString(), doctorId.toString());
                        RedisUtil.hashOps().hset(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER, String.join("_", doctorId.toString(), patientId.toString()),
                                inquirerId.toString());
                        message.setInquirerId(inquirerId);
                    }
                    // 医生未开启付费会话开关
                    if (needCharge == Constants.SESSION_PAY_NO_REQUIRE || isFirstBind) {
                        consultPayType = Constants.CONSULT_PAY_TYPE_FREE;
                        RedisUtil.hashOps().hsetnx(sessionKey, "consultPayType", consultPayType.toString());
                    } else {
                        vo = new SessionVO();
                        // 收费医生姓名
                        vo.setDoctorName(consultPayVo.getDoctorName());
                        vo.setConsultPrice(consultPayVo.getConsultCost());
                        if (!needCharge.equals(PayStatus.PAY_SUCCESS.getValue())) {
                            // 支付失败/待支付
                            if (needCharge.equals(PayStatus.PAY_FAIL.getValue()) || needCharge.equals(PayStatus.WAIT_PAY.getValue())) {
                                vo.setConsultPayStatus(Constants.SESSION_PAY_STATUS_FAIL);
                                return vo;
                            }
                            // 未确认
                            if (needCharge.equals(PayStatus.PAY_NOT_CONFIRMED.getValue())) {
                                vo.setConsultPayStatus(Constants.SESSION_PAY_STATUS_PAYMENT_IN);
                                return vo;
                            }
                        }
                        consultPayType = Constants.CONSULT_PAY_TYPE_CHARGE;
                    }
                }
                Extend exd = message.getExd();
                /*
                 * 如果是患者 扫推荐活动码的首条消息则标识为活动付费
                 */
                if (exd != null && exd.getConsultPayStatus() != null && exd.getConsultPayStatus() == Constants.CONSULT_PAY_TYPE_ACTIVITY_PAY) {
                    consultPayType = Constants.CONSULT_PAY_TYPE_ACTIVITY_PAY;
                }
                if (exd != null && exd.getConsultSourceType() != null) {
                    consultSourceType = exd.getConsultSourceType();
                }
                RedisUtil.hashOps().hsetnx(sessionKey, "sessionId", sessionId);
                // 创建新的session,并返回结果
                vo = createSession(sessionKey, sessionId, message, consultPayType, consultSourceType);
                vo.setNewSessionFlag(true);
            } else {
                // 判断并绑定session信息到message
                vo = checkSession(sessionKey, message);
            }
        } else {
            // 如果message有session
            vo = checkSession(sessionKey, message);
        }
        vo.setIsFirst(isFirst);
        vo.setConsultPayType(consultPayType);
        return vo;
    }

    /**
     * 会话处理(正常收费逻辑)
     */
    public SessionVO handleSession(Message<?> message) throws BusinessException {
        return handleSession(message, false);
    }

    /**
     * 创建session,返回结果
     */
    public SessionVO createSession(String sessionKey, String sessionId, Message<?> message, Integer consultPayType, Integer consultSourceType)
            throws NaiteruiIMException {
        // 初始化session信息
        SessionDetails session = new SessionDetails();
        session.setSessionId(sessionId);
        session.setFromId(message.getFrom().getId());
        session.setToId(message.getTo().getId());
        session.setInquirerId(message.getInquirerId());
        session.setBeginTime(message.getSendTime());
        session.setEndTime(0L);
        session.setRelation(message.getRelation());
        // 1:咨询中
        session.setPayStatus(IN_ASK);
        // 付费咨询是否收费
        session.setConsultPayType(consultPayType);
        session.setConsultSourceType(consultSourceType);

        // 会话首条消息session
        SessionDetails.Msg firstMsg = new SessionDetails.Msg();
        Integer clinicFirst = 0;
        if (message != null) {
            int messageType = message.getType();
            firstMsg.setType(messageType);
            if (messageType == Constants.MESSAGE_TYPE_MEDIA_AUDIO || messageType == Constants.MESSAGE_TYPE_MEDIA_IMG || messageType == Constants.MESSAGE_TYPE_MEDIA_VIDEO) {
                // 转换为多媒体消息内容对象
                Media media = (Media) message.getContent();
                // 截取掉域名
                media.setPath(media.getPath().replaceAll(mediaConfig.getServerDomain(), ""));
            }
            firstMsg.setContent(message.getContent());
            LOGGER.info("发消息创建会话，消息类型 {}", messageType);
            if (messageType == Constants.MESSAGE_TYPE_REMIND_PATIENTS_FILL_MEDICAL_RECORD
                    && ((CustomizedMessage) message.getContent()).getSpecificMessageType() == CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_CLINICFIRST_RECORD) {
                LOGGER.info("发消息创建会话，专家首条消息！");
                clinicFirst = 1;
            }
        }
        session.setClinicFirst(clinicFirst);
        session.setFirstMsg(firstMsg);

        // 创建账单请求
        createBillHandle(session);

        // 保存session信息 到redis
        saveSessionToRedis(sessionKey, session);

        // 保存session到mongo
        sessionDao.saveSesssion(session);

        // 绑定session信息
        bindSession(message, sessionId, session.getBeginTime(), session.getPayType(), consultPayType, session.getConsultSourceType(), clinicFirst);

        // 未失效的购药咨询平移（再次推送，库中只有一份该信息数据）
        // 2.5.0 购药咨询不失效，系统无自动推荐，消息不平移
        // handleRequireMessage(session);

        SessionVO vo = new SessionVO();
        vo.setSessionId(sessionId);
        vo.setBeginTime(session.getBeginTime());
        vo.setConsultSessionId(session.getConsultSessionId());
        vo.setConsultSourceType(session.getConsultSourceType());
        LOGGER.debug("create session,sessionId is {}", sessionId);
        return vo;
    }

    /**
     * 创建付费咨询session,返回结果
     */
    public SessionVO createConsultSession(Long doctorId, Long patientId) throws NaiteruiIMException {
        SessionVO sessionVo = new SessionVO();
        sessionVo.setFromId(patientId);// 设置为来自患者
        sessionVo.setToId(doctorId);
        sessionVo.setRelation(Constants.MESSAGE_RESOURCE_PATIENT);// 会话关系 P2D
        String sessionKey = getSessionKey(doctorId, patientId);
        Long beginTime = System.currentTimeMillis();
        String sessionId = sessionKey + "_" + beginTime;
        sessionVo.setSessionId(sessionId);

        // 初始化session信息
        SessionDetails session = new SessionDetails();
        session.setSessionId(sessionId);
        session.setFromId(sessionVo.getFromId());
        session.setToId(sessionVo.getToId());
        session.setInquirerId(Long.parseLong(RedisUtil.hashOps().hget(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER, String.join("_" , doctorId.toString(), patientId.toString()))));
        session.setBeginTime(beginTime);
        session.setEndTime(0L);
        session.setRelation(sessionVo.getRelation());
        session.setPayStatus(IN_ASK); // 1:咨询中
        session.setConsultPayType(Constants.CONSULT_PAY_TYPE_CHARGE);// 付费咨询是否收费
        session.setConsultSourceType(SessionDetails.CONSULT_TYPE_GENERAL);// 会话来源类型

        // 会话首条消息session
        SessionDetails.Msg firstMsg = new SessionDetails.Msg();
        session.setFirstMsg(firstMsg);

        // 创建账单请求
        createBillHandle(session);

        // 保存session信息 到redis
        saveSessionToRedis(sessionKey, session);

        // 保存session到mongo
        sessionDao.saveSesssion(session);

        sessionVo.setSessionId(sessionId);
        sessionVo.setBeginTime(session.getBeginTime());
        sessionVo.setConsultSessionId(session.getConsultSessionId());
        sessionVo.setConsultPayStatus(Constants.CONSULT_PAY_TYPE_CHARGE);
        LOGGER.debug("create session,sessionId is {},ConsultSessionId:{}", sessionId, sessionVo.getConsultSessionId());
        return sessionVo;
    }

    /**
     * 设置会话状态
     *
     * @param sessionId
     * @param doctorId
     * @param patientId
     */
    private void setConsultSessionStatus(String sessionId, Long doctorId, Long patientId){
        boolean hexist = RedisUtil.hashOps().hexist(CommonConstant.CONSULT_DOCTOR_SEND_MESSAGE_CACHE, sessionId);
        if (hexist) {
            return;
        }
        // 修改会话状态为已接诊
        doctorFeginClient.updateSessionStatus(sessionId, 2);
        RedisUtil.hashOps().hset(CommonConstant.CONSULT_DOCTOR_SEND_MESSAGE_CACHE, sessionId, doctorId + "_" + patientId);
    }

    /**
     * 检查患者端发送到第一条消息长度
     */
    private int checkFirstMsg(boolean isFromDct, Long doctorId, Long patientId, String sessionKey) throws NaiteruiIMException {
        // 如果会话存在,说明不是第一条消息
        if (isFromDct && RedisUtil.keyOps().existsKey(Constants.CACHE_PATIENT_FIRST + patientId)) {
            return NOT_FIRST_MSG;
        }

        long messageCount = messageDao.queryCountByCondition(Constants.MESSAGE_RESOURCE_PATIENT, patientId, null);
        long millis = DateUtil.convertDate(DateUtil.addDays(new Date(), 1)).getTime() - System.currentTimeMillis();
        RedisUtil.valueOps().set(Constants.CACHE_PATIENT_FIRST + patientId, sessionKey, Long.valueOf(millis / 1000).intValue());
        LOGGER.debug("[checkFirstMsg] messageCount = {},doctorId = {},patientId = {}", messageCount, doctorId, patientId);

        if (messageCount <= 0) {
            return IS_FIRST_MSG;
        }
        return NOT_FIRST_MSG;
    }

    /**
     * 检查session,如果信息符合
     */
    private SessionVO checkSession(String sessionKey, Message<?> message) {
        SessionVO vo = new SessionVO();
        String sessionId = null;
        Long beginTime = 0L;
        Long consultSessionId = 0L;
        Integer consultSourceType = null;
        int loop = 0;
        do {
            sessionId = RedisUtil.hashOps().hget(sessionKey, "sessionId");
            beginTime = NumberUtils.toLong(RedisUtil.hashOps().hget(sessionKey, "beginTime"));
            consultSessionId = NumberUtils.toLong(RedisUtil.hashOps().hget(sessionKey, "consultSessionId"));
            consultSourceType = NumberUtils.toInt(RedisUtil.hashOps().hget(sessionKey, "consultSourceType"));
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                LOGGER.error("checkSession ERROR ; create session sleep loop error : sessionKey = {}", sessionKey, e);
            }
        } while (loop++ < 3 && (StringUtils.isBlank(sessionId) || beginTime.longValue() == 0));

        Long currTime = System.currentTimeMillis();
        // 如果redis里没有session,或者session已超时
        if (sessionId == null || currTime - beginTime >= mqttConnectionConfig.getSessionTimeout()) {
            sessionId = StringUtils.isBlank(sessionId) ? message.getSession().getSessionId() : sessionId;
            SessionDetails session = sessionDao.findSessionById(sessionId);
            Long endTime = session == null ? 0 : session.getEndTime();
            vo.setFinished(true);
            vo.setEndTime(endTime);
        } else {
            Integer payType = NumberUtils.toInt(RedisUtil.hashOps().hget(sessionKey, "payType"));
            Integer consultPayType = NumberUtils.toInt(RedisUtil.hashOps().hget(sessionKey, "consultPayType"));
            Integer clinicFirst = NumberUtils.toInt(RedisUtil.hashOps().hget(sessionKey, "clinicFirst"));
            // 绑定session信息
            bindSession(message, sessionId, beginTime, payType, consultPayType, consultSourceType, clinicFirst);
        }
        vo.setSessionId(sessionId);
        vo.setBeginTime(beginTime);
        vo.setConsultSessionId(consultSessionId);
        vo.setConsultSourceType(consultSourceType);
        LOGGER.info("check session,session info:{}", JSONUtil.toJsonStr(vo));
        return vo;
    }

    /**
     * 获取 sessionkey
     */
    public static String getSessionKey(Long doctorId, Long patientId) {
        return doctorId + "_" + patientId;
    }

    /**
     * 保存session信息到redis
     */
    private void saveSessionToRedis(String key, SessionDetails session) throws NaiteruiIMException {
        // 修改 session状态为已支付
        Map<String, String> hmap = new HashMap<>();
        hmap.put("disclaimer", Boolean.FALSE.toString());
        hmap.put("sessionId", session.getSessionId());
        hmap.put("beginTime", session.getBeginTime() + "");
        hmap.put("payType", session.getPayType() + "");
        hmap.put("consultSessionId", session.getConsultSessionId() + "");
        hmap.put("consultPayType", session.getConsultPayType() == null ? null : session.getConsultPayType().toString());
        hmap.put("consultSourceType", session.getConsultSourceType() == null ? null : session.getConsultSourceType().toString());
        hmap.put("clinicFirst", null == session.getClinicFirst() ? null : session.getClinicFirst() + "");

        // 保存session信息到reids
        RedisUtil.hashOps().hmset(key, hmap);

        // 设置过期时间,单位(秒),session超时时间+30分钟
        int seconds = mqttConnectionConfig.getSessionTimeout() / 1000 + 1800;
        RedisUtil.keyOps().expire(key, seconds);

    }

    /**
     * 消息体绑定session
     */
    private void bindSession(Message<?> message, String sessionId, Long beginTime, Integer payType, Integer consultPayType,
                             Integer consultSourceType, Integer clinicFirst) {
        // 邦定seesion信息,到message
        if (message.getSession() == null) {
            message.setSession(new Session());
        }
        message.getSession().setSessionId(sessionId);
        message.getSession().setBeginTime(beginTime);
        message.getSession().setPayType(payType);
        message.getSession().setConsultPayType(consultPayType);// 付费咨询是否需要付费
        message.getSession().setConsultSourceType(consultSourceType);
        message.getSession().setClinicFirst(null != clinicFirst ? clinicFirst : 0);
    }

    /**
     * 创建账单请求
     */
    private void createBillHandle(SessionDetails session) {
        try {
            // 请求cms创建session账单
            Map<String, String> result = remoter.createSessionBill(session);
            // 设置支付类型,0:免费;1:收费
            session.setPayType(Integer.parseInt(result.get("costType")));
            session.setPrice(result.get("cost"));
            // 医生端会话存储会话id
            session.setConsultSessionId(Long.parseLong(result.get("consultSessionId")));
        } catch (Exception e) {
            LOGGER.info("createBill failure,session is {}", JSON.toJSONString(session), e);
        }
    }

    /**
     * 分页 查询session记录
     */
    public PageVO<SessionDetails> findSessionPageList(SessionParams query) throws NaiteruiIMException {

        Long fromId = query.getDoctorId();
        Long toId = query.getPatientId();

        // 排序条件
        String orderBy = query.getOrderBy() == null || query.getOrderBy() == 0 ? Page.ASC : Page.DESC;

        // 查询出当页数据
        List<SessionDetails> result = sessionDao.pageQuery(
                SessionConditionVO
                        .builder()
                        .fromId(fromId)
                        .toId(toId)
                        .relation(Constants.MESSAGE_RESOURCE_DOCTOR)
                        .pageNo(query.getPageNo())
                        .pageSize(query.getPageSize())
                        .orderby(orderBy)
                        .orderbyItem("beginTime")
                        .status(query.getStatus())
                        .consultPayType(null)
                        .gtTime(null)
                        .ltTime(null)
                        .build());
        // 医生端不返回firtstMsg
        for (SessionDetails sessionDetail : result) {
            sessionDetail.setFirstMsg(null);
        }
        // 查询出总条数
        long totalCount = sessionDao.queryCount(
                SessionConditionCountVO
                        .builder()
                        .fromId(fromId)
                        .toId(toId)
                        .relation(Constants.MESSAGE_RESOURCE_DOCTOR)
                        .status(query.getStatus())
                        .consultPayType(null)
                        .gtTime(null)
                        .ltTime(null)
                        .build());

        // 算出总页数
        long totalPage = totalCount / query.getPageSize() + (totalCount % query.getPageSize() > 0 ? 1 : 0);

        PageVO<SessionDetails> pageVO = new PageVO<>();
        pageVO.setPageNo(query.getPageNo());
        pageVO.setPageSize(query.getPageSize());
        pageVO.setResult(result);
        pageVO.setTotalCount((int) totalCount);
        pageVO.setTotalPage((int) totalPage);
        pageVO.setOrderBy(orderBy);
        return pageVO;
    }

    /**
     * 结束会话
     */
    public Long finishSession(String sessionId, Long doctorId, Long patientId, Integer freeFlg, Boolean isManual, Boolean isDeletePatient,
                              SessionDetails sessionDetails, Integer triggerSource, Long... sourceEndTime)
            throws NaiteruiIMException {
        String sessionKey = getSessionKey(doctorId, patientId);
        if (!RedisUtil.keyOps().existsKey(sessionKey)) {
            LOGGER.info("结束会话缓存已经清除，sessionId:{}", sessionId);
        }
        // 删除redis中对应的sessionId
        RedisUtil.keyOps().delete(sessionKey);
        LOGGER.debug("session {} is finished.", sessionId);
        /*
         * 删除单次会话中患者发过消息的标识
         */
        RedisUtil.keyOps().delete(Constants.CACHE_PATIENT_EXSIT_MESSAGE_IN_A_SESSION + doctorId + "_" + patientId);
        LOGGER.info("结束会话,sessionId:{},isManual:{},isDeletePatient:{},triggerSource:{},sourceEndTime:{}", sessionId, isManual, isDeletePatient,
                    triggerSource, sourceEndTime);

        // 删除首次绑定的逻辑
        boolean existsKey = RedisUtil.keyOps().existsKey(CommonConstant.PATIENT_PERSONAL_DOCTOR_FIRST_BIND + doctorId + "_" + patientId);
        LOGGER.info("PATIENT_PERSONAL_DOCTOR_FIRST_BIND === {}", existsKey);
        if(existsKey){
            RedisUtil.keyOps().delete(CommonConstant.PATIENT_PERSONAL_DOCTOR_FIRST_BIND + doctorId + "_" + patientId);
        }

        // 会话 结束时间
        Long endTime = sourceEndTime.length == 0 ? System.currentTimeMillis() : sourceEndTime[0];
        Integer consultSourceType = sessionDetails.getConsultSourceType();
        sessionDetails.setEndTime(endTime);
        remoter.finishSessionDrNotice(doctorId, sessionDetails);

        // 获取 最后一条消息
        Message<?> message = messageDao.findLastRecord(doctorId, patientId, endTime, Constants.MESSAGE_RESOURCE_DOCTOR);

        // 保存最后一条消息到session
        SessionDetails.Msg lastMsg = new SessionDetails.Msg();

        // 群发不存最后一条消息
        if (message != null && (message.getExd() == null || message.getExd().getBatchId() == null)) {
            lastMsg.setType(message.getType());
            lastMsg.setContent(message.getContent());
        }

        sessionDao.updateSession(sessionId, endTime, NOT_PAY, lastMsg);
        // 结束会话账单
        finishBillHandle(sessionId, endTime, freeFlg, isManual, isDeletePatient);

        SessionCloseBO sessionCloseBO = new SessionCloseBO(doctorId, patientId, sessionId, consultSourceType, null, !isManual);


        // 根据SESSION查询是否包含用户主动有效消息
        long count = messageDao.queryConsumerMessageCount(Constants.MESSAGE_RESOURCE_DOCTOR, doctorId, patientId, sessionDetails.getBeginTime(),
                                                               endTime);
        Boolean isSessionValid = null;
        switch (triggerSource) {
            case Constants.SESSION_CLOSE_TRIGGER_SOURCE_AUTO:// 自动结束
            case Constants.SESSION_CLOSE_TRIGGER_SOURCE_DELETE_PATIENT:// 删除患者
            case Constants.SESSION_CLOSE_TRIGGER_SOURCE_DOCTOR_MANUAL:// 医生手动结束
                isSessionValid = count > 0;
                break;
            //cms禁用医生，所有会话都退费
            case ImConstants.SESSION_CLOSE_TRIGGER_SOURCE_DISABLED_DOCTOR:
                isSessionValid = false;
                break;
            default:
                isSessionValid = count > 0;
                break;
        }

        // 发送有效会话通知
        noticeSysMsgType(sessionCloseBO, isSessionValid);

        // 清除医患信息就诊人就诊人
        RedisUtil.hashOps().hdel(CommonConstant.CONSULT_PATIENT_AIM_INQUIRER, String.join("_" , doctorId.toString(), patientId.toString()));
        return endTime;
    }

    /**
     * 自动结束会话
     */
    public void autofinishSession(String sessionId) {
        try {
            SessionDetails session = sessionDao.findSessionById(sessionId);

            // 根据redis中sessionId,判断当前会话是否已经被手动结束
            if (session != null && session.getEndTime() == 0) {
                // 是否来源于医生端
                boolean isFromDct = session.getRelation() == Constants.MESSAGE_RESOURCE_DOCTOR;
                Long doctorId = isFromDct ? session.getFromId() : session.getToId();
                Long patientId = isFromDct ? session.getToId() : session.getFromId();

                // 按照结束时间
                Long endTime = session.getBeginTime() + mqttConnectionConfig.getSessionTimeout();
                finishSession(session.getSessionId(), doctorId, patientId, session.getPayType(), false, false, session,
                                   Constants.SESSION_CLOSE_TRIGGER_SOURCE_AUTO, endTime);

            }
        } catch (Exception e) {
            LOGGER.info("finish session failure,sessionId is {}", sessionId, e);
        }
    }

    /**
     * 结束会话,账单请求
     */
    private void finishBillHandle(String sessionId, Long endTime, Integer freeFlg, Boolean isManual, Boolean isDeletePatient) {
        LOGGER.info("结束会话,账单请求,sessionId:{},endTime:{},freeFlg:{},isManual:{},isDeletePatient:{}", sessionId, endTime, freeFlg, isManual,
                    isDeletePatient);
        try {

            // 结束session账单
            Map<String, String> result = remoter.finishSessionBill(sessionId, endTime, freeFlg, isManual, isDeletePatient);
            int payStatus = Integer.parseInt(result.get("payStatus"));
            Long consultId = Long.parseLong(result.get("consultId"));
            sessionDao.updatePayStatus(sessionId, payStatus);

            // 推送患者端APP结束会话通知
            SessionDetails session = sessionDao.findSessionById(sessionId);
            boolean isFromDct = session.getRelation() == Constants.MESSAGE_RESOURCE_DOCTOR;
            Long patientId = isFromDct ? session.getToId() : session.getFromId();
            remoter.finishSessionPtNotice(patientId, session, consultId);

        } catch (Exception e) {
            LOGGER.error("结束会话,账单请求,sessionId:{},endTime:{},freeFlg:{},isManual:{},isDeletePatient:{}", sessionId, endTime, freeFlg, isManual,
                         isDeletePatient);
        }
    }

    /**
     * 发送会话是否仅包含系统消息确认通知到患者端
     */
    private void noticeSysMsgType(SessionCloseBO sessionCloseBO, Boolean isValid) {
        // 查询会话是否包含非系统回复
        try {
            if (isValid) {// 包含有效回复
                remoter.finishValidSession(sessionCloseBO.getSessionId(), sessionCloseBO.getDoctorId(), sessionCloseBO.getPatientId(),
                                                sessionCloseBO.getConsultSourceType());
                LOGGER.info("[finish session]结束有效会话  sessionInfo:{},isValid:{}", sessionCloseBO.toString(), isValid);
            } else {
                remoter.finishInvalidSession(sessionCloseBO.getSessionId(), sessionCloseBO.getDoctorId(), sessionCloseBO.getPatientId(),
                                                  sessionCloseBO.getConsultSourceType());
                LOGGER.info("[finish session]结束无效会话  sessionInfo:{},isValid:{}", sessionCloseBO.toString(), isValid);

            }
        } catch (Exception e) {
            LOGGER.error("[finish session]结束会话处理是否有效异常  sessionInfo:{},isValid:{},{}", sessionCloseBO.toString(),
                isValid, e);
        }
    }


    /**
     * 更新session状态为已支付
     */
    public void sessionPaid(String sessionId) throws NaiteruiIMException {
        // 修改 session状态为已支付
        sessionDao.updatePayStatus(sessionId, HAVE_PAID);
    }

    /**
     * 修改支付类型
     */
    public SessionVO findSessionStatus(String sessionId) throws NaiteruiIMException {

        SessionVO result = new SessionVO();

        if (StringUtils.isNotBlank(sessionId)) {
            SessionDetails session = sessionDao.findSessionById(sessionId);
            // 返回session状态和当前系统 时间
            result.setStatus(session.getPayStatus());
        }
        result.setSysTime(System.currentTimeMillis());

        return result;
    }

    /**
     * 查询出会话周期内未结束 的会话
     */
    public List<SessionDetails> findSessionByTime(Long beginTime) {
        List<SessionDetails> result = sessionDao.findSessionByTime(beginTime);
        return result;
    }

    /**
     * SessionHandler.updateContains()
     */
    public void updateContains(String sessionId, Integer containRecom, Integer containRecord) {
        if (StringUtils.isBlank(sessionId)) {
            return;
        }
        boolean recomFlag = false;
        boolean recordFlag = false;
        if (null != containRecom && SessionDetails.CONTAIN_RECOM_YES == containRecom) {
            recomFlag = true;
        }
        if (null != containRecord && SessionDetails.CONTAIN_RECORD_YES == containRecord) {
            recordFlag = true;
        }
        sessionDao.updateContains(sessionId, recomFlag, recordFlag);
    }

    public PageVO<SessionDetailBO> sessionPageList(SessionParamBO params) {

        Long doctorId = params.getDoctorId();
        //0：全部，1：免费，2：收费
        Integer consultPayType = null;
        if (params.getFeeType() != null) {
            switch (params.getFeeType()) {
                case SessionParamBO.FEE_TYPE_FREE:
                    consultPayType = Constants.CONSULT_PAY_TYPE_FREE;
                    break;
                case SessionParamBO.FEE_TYPE_PAY:
                    consultPayType = Constants.CONSULT_PAY_TYPE_CHARGE;
                    break;
                default:
                    consultPayType = null;
                    break;
            }
        }

        // 排序条件
        String orderBy = Page.DESC;

        // 查询出当页数据
        List<SessionDetails> result = sessionDao.pageQuery(
                SessionConditionVO
                        .builder()
                        .fromId(doctorId)
                        .toId(null)
                        .inquirerIds(params.getInquirerIds())
                        .relation(Constants.MESSAGE_RESOURCE_DOCTOR)
                        .pageNo(params.getPageNo())
                        .pageSize(params.getPageSize())
                        .orderby(orderBy)
                        .orderbyItem("beginTime")
                        .status(null)
                        .consultPayType(consultPayType)
                        .gtTime(params.getBeginGt())
                        .ltTime(params.getBeginLt())
                        .containRecord(params.getContainRecord())
                        .build());

        // 查询出总条数
        long totalCount = sessionDao.queryCount(
                SessionConditionCountVO
                        .builder()
                        .fromId(doctorId)
                        .toId(null)
                .inquirerIds(params.getInquirerIds())
                        .relation(Constants.MESSAGE_RESOURCE_DOCTOR)
                        .status(null)
                        .consultPayType(consultPayType)
                        .gtTime(params.getBeginGt())
                        .ltTime(params.getBeginLt())
                .containRecord(params.getContainRecord())
                        .build());

        // 算出总页数
        long totalPage = totalCount / params.getPageSize() + (totalCount % params.getPageSize() > 0 ? 1 : 0);

        List<SessionDetailBO> newResult = result.stream().map(sessionDetails -> {
            Long patientId = Constants.MESSAGE_RESOURCE_DOCTOR == sessionDetails.getRelation() ? sessionDetails.getToId() :
                    sessionDetails.getFromId();
            return SessionDetailBO.builder()
                                  .beginTime(sessionDetails.getBeginTime())
                                  .free(null == sessionDetails.getConsultPayType() || sessionDetails.getConsultPayType()
                                                                                                    .equals(Constants.CONSULT_PAY_TYPE_FREE))
                                  .consultSessionId(sessionDetails.getConsultSessionId())
                                  .containRecom(null != sessionDetails.getContainRecom() && sessionDetails.getContainRecom()
                                                                                                          .equals(SessionDetails.CONTAIN_RECOM_YES))
                                  .containRecord(null != sessionDetails.getContainRecom() && sessionDetails.getContainRecom()
                                                                                                           .equals(SessionDetails.CONTAIN_RECOM_YES))
                                  .patientId(patientId)
                                  .doctorId(doctorId)
                                  .firstContent(overViewMsg(sessionDetails.getFirstMsg()))
                                  .endTime(sessionDetails.getEndTime())
                                  .sessionId(sessionDetails.getSessionId())
                                  .build();
        }).collect(Collectors.toList());

        PageVO<SessionDetailBO> pageVO = new PageVO<>();
        pageVO.setPageNo(params.getPageNo());
        pageVO.setPageSize(params.getPageSize());
        pageVO.setResult(newResult);
        pageVO.setTotalCount((int) totalCount);
        pageVO.setTotalPage((int) totalPage);
        pageVO.setOrderBy(orderBy);
        return pageVO;
    }

    private String overViewMsg(SessionDetails.Msg msg) {
        if (msg == null || msg.getType() == null) {
            return "";
        }
        String viewMsg;
        switch (msg.getType()) {
            case Constants.MESSAGE_TYPE_TEXT:
                viewMsg = StringUtils.trimToEmpty(msg.getContent() + "");
                break;
            case Constants.MESSAGE_TYPE_MEDIA_IMG:
                viewMsg = "[图片消息]";
                break;
            case Constants.MESSAGE_TYPE_MEDIA_AUDIO:
                viewMsg = "[语音消息]";
                break;
            case Constants.MESSAGE_TYPE_MEDIA_VIDEO:
                viewMsg = "[小视频]";
                break;
            case Constants.MESSAGE_TYPE_DRUG:
                viewMsg = "[处方消息]";
                break;
            case Constants.MESSAGE_TYPE_PAID_CONSULT:
                viewMsg = "[付费问诊]";
                break;
            case Constants.MESSAGE_TYPE_REMIND_PATIENTS_FILL_MEDICAL_RECORD:
                CustomizedMessage cMsg = (CustomizedMessage) msg.getContent();
                if (cMsg == null || cMsg.getSpecificMessageType() == null) {
                    viewMsg = "";
                    break;
                }
                switch (cMsg.getSpecificMessageType()) {
                    case CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_MEDICAL_RECORD:
                        viewMsg = "[患者病历]";
                        break;
                    case CustomizedMessage.SPEC_MESS_TYPE_PATIENTS_DISEASE_RECORD:
                        viewMsg = "[患者病情描述]";
                        break;
                    default:
                        viewMsg = "";
                        break;
                }
                break;
            default:
                viewMsg = "";
                break;
        }
        return viewMsg;
    }

    /**
     * 获取会话结束时间
     * @param sessionId
     * @return
     */
    public Long getSessionEndTime(String sessionId){
        SessionDetails session = sessionDao.findSessionById(sessionId);
        return session == null ? 0 : session.getEndTime();
    }
}
